<?php

namespace app\service;

use Exception;

class EncryptionService
{
    private static $supportedMethods = [
        'AES-256-GCM',
        'AES-256-CBC',
        'AES-128-GCM',
        'AES-128-CBC',
        'ChaCha20-Poly1305'
    ];

    // OpenSSL实际使用的方法名映射
    private static $opensslMethodMap = [
        'AES-256-GCM' => 'aes-256-gcm',
        'AES-256-CBC' => 'aes-256-cbc',
        'AES-128-GCM' => 'aes-128-gcm',
        'AES-128-CBC' => 'aes-128-cbc',
    ];

    /**
     * 获取支持的加密方式列表
     */
    public static function getSupportedMethods()
    {
        return self::$supportedMethods;
    }

    /**
     * 加密数据
     */
    public static function encrypt($data, $method = 'AES-256-GCM', $masterKey = null)
    {
        if (empty($data)) {
            return '';
        }

        if (!in_array($method, self::$supportedMethods)) {
            throw new Exception("不支持的加密方式: {$method}");
        }

        // 使用用户主密钥或系统默认密钥
        $key = $masterKey ?: (function_exists('config') ? config('app.encrypt_key', 'default_secret_key_32_characters!') : 'default_secret_key_32_characters!');

        // 确保密钥长度合适
        $key = self::prepareKey($key, $method);

        try {
            switch ($method) {
                case 'AES-256-GCM':
                case 'AES-128-GCM':
                    $opensslMethod = self::$opensslMethodMap[$method];
                    return self::encryptGCM($data, $key, $opensslMethod);

                case 'AES-256-CBC':
                case 'AES-128-CBC':
                    $opensslMethod = self::$opensslMethodMap[$method];
                    return self::encryptCBC($data, $key, $opensslMethod);

                case 'ChaCha20-Poly1305':
                    return self::encryptChaCha20($data, $key);

                default:
                    throw new Exception("加密方式 {$method} 未实现");
            }
        } catch (Exception $e) {
            throw new Exception("加密失败: " . $e->getMessage());
        }
    }

    /**
     * 解密数据
     */
    public static function decrypt($encryptedData, $method = 'AES-256-GCM', $masterKey = null)
    {
        if (empty($encryptedData)) {
            return '';
        }

        if (!in_array($method, self::$supportedMethods)) {
            throw new Exception("不支持的加密方式: {$method}");
        }

        // 使用用户主密钥或系统默认密钥
        $key = $masterKey ?: (function_exists('config') ? config('app.encrypt_key', 'default_secret_key_32_characters!') : 'default_secret_key_32_characters!');

        // 确保密钥长度合适
        $key = self::prepareKey($key, $method);

        try {
            switch ($method) {
                case 'AES-256-GCM':
                case 'AES-128-GCM':
                    $opensslMethod = self::$opensslMethodMap[$method];
                    return self::decryptGCM($encryptedData, $key, $opensslMethod);

                case 'AES-256-CBC':
                case 'AES-128-CBC':
                    $opensslMethod = self::$opensslMethodMap[$method];
                    return self::decryptCBC($encryptedData, $key, $opensslMethod);

                case 'ChaCha20-Poly1305':
                    return self::decryptChaCha20($encryptedData, $key);

                default:
                    throw new Exception("解密方式 {$method} 未实现");
            }
        } catch (Exception $e) {
            throw new Exception("解密失败: " . $e->getMessage());
        }
    }

    /**
     * 准备密钥
     */
    private static function prepareKey($key, $method)
    {
        $keyLength = self::getKeyLength($method);

        if (strlen($key) < $keyLength) {
            // 如果密钥太短，用PBKDF2扩展
            return hash_pbkdf2('sha256', $key, 'account_manage_salt', 10000, $keyLength, true);
        } elseif (strlen($key) > $keyLength) {
            // 如果密钥太长，截取
            return substr($key, 0, $keyLength);
        }

        return $key;
    }

    /**
     * 获取密钥长度
     */
    private static function getKeyLength($method)
    {
        switch ($method) {
            case 'AES-256-GCM':
            case 'AES-256-CBC':
            case 'ChaCha20-Poly1305':
                return 32; // 256位
            case 'AES-128-GCM':
            case 'AES-128-CBC':
                return 16; // 128位
            default:
                return 32;
        }
    }

    /**
     * GCM模式加密
     */
    private static function encryptGCM($data, $key, $method)
    {
        $iv = random_bytes(12); // GCM推荐96位IV
        $tag = '';

        $encrypted = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv, $tag);

        if ($encrypted === false) {
            throw new Exception("GCM加密失败");
        }

        // 返回: base64(iv + tag + encrypted_data)
        return base64_encode($iv . $tag . $encrypted);
    }

    /**
     * GCM模式解密
     */
    private static function decryptGCM($encryptedData, $key, $method)
    {
        $data = base64_decode($encryptedData);

        if ($data === false || strlen($data) < 28) { // 12(IV) + 16(TAG) = 28
            throw new Exception("GCM数据格式错误");
        }

        $iv = substr($data, 0, 12);
        $tag = substr($data, 12, 16);
        $encrypted = substr($data, 28);

        $decrypted = openssl_decrypt($encrypted, $method, $key, OPENSSL_RAW_DATA, $iv, $tag);

        if ($decrypted === false) {
            throw new Exception("GCM解密失败");
        }

        return $decrypted;
    }

    /**
     * CBC模式加密
     */
    private static function encryptCBC($data, $key, $method)
    {
        $ivLength = openssl_cipher_iv_length($method);
        $iv = random_bytes($ivLength);

        $encrypted = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);

        if ($encrypted === false) {
            throw new Exception("CBC加密失败");
        }

        // 返回: base64(iv + encrypted_data)
        return base64_encode($iv . $encrypted);
    }

    /**
     * CBC模式解密
     */
    private static function decryptCBC($encryptedData, $key, $method)
    {
        $data = base64_decode($encryptedData);

        if ($data === false) {
            throw new Exception("CBC数据格式错误");
        }

        $ivLength = openssl_cipher_iv_length($method);

        if (strlen($data) < $ivLength) {
            throw new Exception("CBC数据长度不足");
        }

        $iv = substr($data, 0, $ivLength);
        $encrypted = substr($data, $ivLength);

        $decrypted = openssl_decrypt($encrypted, $method, $key, OPENSSL_RAW_DATA, $iv);

        if ($decrypted === false) {
            throw new Exception("CBC解密失败");
        }

        return $decrypted;
    }

    /**
     * ChaCha20-Poly1305加密
     */
    private static function encryptChaCha20($data, $key)
    {
        if (!function_exists('sodium_crypto_aead_chacha20poly1305_encrypt')) {
            throw new Exception("ChaCha20-Poly1305需要sodium扩展");
        }

        $nonce = random_bytes(SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES);
        $encrypted = sodium_crypto_aead_chacha20poly1305_encrypt($data, '', $nonce, $key);

        // 返回: base64(nonce + encrypted_data)
        return base64_encode($nonce . $encrypted);
    }

    /**
     * ChaCha20-Poly1305解密
     */
    private static function decryptChaCha20($encryptedData, $key)
    {
        if (!function_exists('sodium_crypto_aead_chacha20poly1305_decrypt')) {
            throw new Exception("ChaCha20-Poly1305需要sodium扩展");
        }

        $data = base64_decode($encryptedData);

        if ($data === false || strlen($data) < SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES) {
            throw new Exception("ChaCha20数据格式错误");
        }

        $nonce = substr($data, 0, SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES);
        $encrypted = substr($data, SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES);

        $decrypted = sodium_crypto_aead_chacha20poly1305_decrypt($encrypted, '', $nonce, $key);

        if ($decrypted === false) {
            throw new Exception("ChaCha20解密失败");
        }

        return $decrypted;
    }

    /**
     * 验证加密方式是否可用
     */
    public static function isMethodAvailable($method)
    {
        if (!in_array($method, self::$supportedMethods)) {
            return false;
        }

        switch ($method) {
            case 'ChaCha20-Poly1305':
                return function_exists('sodium_crypto_aead_chacha20poly1305_encrypt');
            default:
                // 使用映射的OpenSSL方法名
                $opensslMethod = self::$opensslMethodMap[$method] ?? $method;
                return in_array($opensslMethod, openssl_get_cipher_methods());
        }
    }

    /**
     * 获取当前系统可用的加密方式
     */
    public static function getAvailableMethods()
    {
        $available = [];

        foreach (self::$supportedMethods as $method) {
            if (self::isMethodAvailable($method)) {
                $available[] = $method;
            }
        }

        return $available;
    }
}